//      dlm.c
//      
//      Copyright 2012 Piotr <piotr@piotr-dell>
//      
//      This program is free software; you can redistribute it and/or modify
//      it under the terms of the GNU General Public License as published by
//      the Free Software Foundation; either version 2 of the License, or
//      (at your option) any later version.
//      
//      This program is distributed in the hope that it will be useful,
//      but WITHOUT ANY WARRANTY; without even the implied warranty of
//      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//      GNU General Public License for more details.
//      
//      You should have received a copy of the GNU General Public License
//      along with this program; if not, write to the Free Software
//      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
//      MA 02110-1301, USA.


#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <poll.h>
#include <string.h>
#include <pty.h>

#include <time.h>
#include <signal.h>


#include "dlm.h"
#include "dlmlists.h"
#include "common.h"

struct pollfd pipesToPoll[PROCESS_COUNT];
int activeProcesses;

int main(int argc, char** argv)
{
	printf("DLM\n");
	if (argc != 2) {
		printf("Uzywanie: ./dlm x gdzie x to liczba procesów potomnych\n");
		return DLM_ERR_UKNOWN;
	}
	int processesToFork;
	sscanf(argv[1], "%d", &processesToFork);
	if (processesToFork > PROCESS_COUNT) {
		printf("Za duzo procesów potomnych, max %d\n", PROCESS_COUNT);
		return DLM_ERR_UKNOWN;
	}
	
	for(int i = 0; i<processesToFork; ++i)
		DLM_fork();
	return DLM_main_loop();
}

int DLM_init()
{
	return DLM_ERR_OK;
}

int DLM_main_loop() {
	int i;
	int result;
	int processed;
	message request;
	
	result = DLM_init();
	if (result < 0)
	{
		printf("[E]DLM_mainloop: Błąd DLM_init\n");
		return DLM_ERR_UKNOWN;
	}

	while(1) {
		printAllLocks();
		result = poll(pipesToPoll, PROCESS_COUNT, -1);
		if (result < 0) {
			printf("[E]DLM_mainloop: Błąd przy poll()\n");
			return DLM_ERR_UKNOWN;
		} else {
			processed = 0;
			
			for(i = 0; i < PROCESS_COUNT; ++i) {
				if(pipesToPoll[i].revents & POLLERR) {
					printf("[E]DLM_mainloop: Błąd przy czytaniu danych poll\n");
				} else if (pipesToPoll[i].revents & POLLIN) {
					read(pipesToPoll[i].fd, &request, sizeof(message));
					printf("Odebrano wiadomość od: %d\n", request.senderPid);
					child_data sender = getChild(request.senderPid);
					if (sender.pid == 0) {
						printf("[E]DLM_mainloop: Błąd przy pobieraniu\
						        procesu potomnego\n");
						continue;
					}
					process_request(request, sender);
					processed++;
				}
				if (processed == result)
					break;
			}
		}
		
	}
	
	return DLM_ERR_OK;
}

pid_t DLM_fork() {
	int error;
	pid_t pid = 0;
	
	struct child_data *new_child =
		(struct child_data*) malloc(sizeof(struct child_data));
	
	error = pipe(new_child->recv_pipe);
	if (error != 0) {
		printf("[E]DLM_fork: Bład przy tworzeniu potoku recv_pipe\n");
		return DLM_ERR_UKNOWN;
	}
	error = pipe(new_child->send_pipe);
		if (error != 0) {
		printf("[E]DLM_fork: Bład przy tworzeniu potoku send_pipe\n");
		return DLM_ERR_UKNOWN;
	}
	
	pid = fork();
	
	if (pid == 0) { //proces potomny
	    //zamykamy niepotrzebne deskryptory
		close(new_child->recv_pipe[PIPE_RECV]);
		close(new_child->send_pipe[PIPE_SEND]);
		
		char pipe1[10], pipe2[10];
		
		char buffer[80];
		
		sprintf(pipe1, "%d", new_child->send_pipe[PIPE_RECV]);
		sprintf(pipe2, "%d", new_child->recv_pipe[PIPE_SEND]);
		
		strcpy(buffer, "\"/home/piotr/Projects/uxp1a/uxp1a-dlm-12z/trunk/client ");
		strcat(buffer, pipe1);
		strcat(buffer, " ");
		strcat(buffer, pipe2);
		strcat(buffer, "\"");
		
		printf("%s\n", buffer);
		
		if(execl("./client", "client", pipe1, pipe2, (char*) 0) != 0) {
		//if(execl("/usr/bin/gnome-terminal", "gnome-terminal","-e", buffer, (char*) 0) != 0) {
			printf("[E]DLM_fork: Błąd przy wywoływaniu procesu (exec)\n");
		}
	} else { //proces macierzysty

		activeProcesses++;
		//zamykamy niepotrzebne deskryptory
		close(new_child->recv_pipe[PIPE_SEND]);
		close(new_child->send_pipe[PIPE_RECV]);

		//dodajemy nowe deskryptory do obserwowanych
		pipesToPoll[activeProcesses].fd = new_child->recv_pipe[PIPE_RECV];
		pipesToPoll[activeProcesses].events = POLLIN;

		new_child->pid = pid;
		addChild(*new_child);
		return pid;
	}
	return DLM_ERR_UKNOWN; //unreachable code
}

int send_reply(struct message messageToSend, struct child_data recipient) {
	if (write(recipient.send_pipe[PIPE_SEND],
	          &messageToSend,
	          sizeof(messageToSend)) != sizeof(messageToSend)) {
				  printf("[E]DLM: Nie udalo sie zapisac do potoku");
				  return DLM_ERR_UKNOWN;
			  }
	return DLM_ERR_OK;
}

int process_request(message request, child_data sender) {
	reqType requestType = (reqType)request.requestType;
	message reply;
	errorCode error;
	lock_waiter waiter;
	lock lockToWait;
	int noResponse = 0;

	if(request.resource_id > RESOURCE_COUNT || request.resource_id < 0) {
		reply.senderPid = getpid();
		reply.requestType = requestType;
		reply.resource_id = request.resource_id;
		reply.lock_type = request.lock_type;
		reply.returnValue = DLM_ERR_NO_SUCH_RESOURCE;
		send_reply(reply, sender);
		return DLM_ERR_NO_SUCH_RESOURCE;
	}

	if(request.lock_type >= DLM_LOCK_COUNT || request.lock_type < 0) {
		reply.senderPid = getpid();
		reply.requestType = requestType;
		reply.resource_id = request.resource_id;
		reply.lock_type = request.lock_type;
		reply.returnValue = DLM_ERR_NO_SUCH_LOCK;
		send_reply(reply, sender);
		return DLM_ERR_NO_SUCH_LOCK;
	}

	switch (requestType) {
	case DLM_REQ_LOCK:
		printf("Obsluga DLM_REQ_LOCK\n");
		lock lockToMake;
		lockToMake.resource_id = request.resource_id;
		lockToMake.lock_type = request.lock_type;
		lockToMake.owner = sender;
		error = addLock(lockToMake);
		if (error == DLM_ERR_CANNOT_LOCK) {
			// Oczekiwanie na zasob:
			if (request.timeout == -1) {
				//non-block, od razu odpowiedź że nie można
				break;
			} else {
				//wstawiamy czekacza do kolejki (bez wysylania odp!)
				lockToWait.resource_id = request.resource_id;
				lockToWait.lock_type = request.lock_type;
				lockToWait.owner = sender;
				waiter.resource_lock = lockToWait;
				waiter.timestamp = current_time_miliseconds();
				waiter.timeout = request.timeout;
				addWaiter(waiter);
				noResponse = 1;
				if (request.timeout != 0) {
					struct sigevent sev;

					//tworzymy nowy timer
					timer_t timerid;
					timer_descriptor *desc = (timer_descriptor*)malloc(sizeof(timer_descriptor));

					sev.sigev_notify = SIGEV_THREAD;
					sev.sigev_notify_function = on_timeout;
					sev.sigev_value.sival_ptr = desc;
					sev.sigev_notify_attributes = NULL;

					timer_create(CLOCK_REALTIME, &sev, &timerid);

					printf("timer ID is 0x%lx\n", (long) timerid);

					itimerspec its;
					its.it_value.tv_sec = request.timeout / 1000;
					its.it_value.tv_nsec = (request.timeout % 1000) * 1000 * 1000;
					its.it_interval.tv_sec = its.it_value.tv_sec;
					its.it_interval.tv_nsec = its.it_value.tv_nsec;

					timer_settime(timerid, 0, &its, NULL);

					printf("Start timera\n");

					desc->timerid = timerid;
					desc->waiter = waiter;

					//odpowiedź do waitera
					desc->waiterReply.senderPid = getpid();
					desc->waiterReply.requestType = DLM_REQ_LOCK;
					desc->waiterReply.resource_id = waiter.resource_lock.resource_id;
					desc->waiterReply.lock_type = lockToWait.lock_type;
					desc->waiterReply.timeout = waiter.timeout;
					desc->waiterReply.returnValue = DLM_ERR_TIMEOUT;

					addTimerDescriptor(waiter.resource_lock.owner.pid, desc);
				}
			}
		}

		break;
	case DLM_REQ_TRYLOCK:
		printf("Obsluga DLM_REQ_TRYLOCK\n");
		lock lockToTry;
		lockToTry.resource_id = request.resource_id;
		lockToTry.lock_type = request.lock_type;
		lockToTry.owner = sender;
		error = tryLock(lockToTry);
		break;
	case DLM_REQ_UNLOCK:
		printf("Obsluga DLM_REQ_UNLOCK\n");
		error = removeLock(sender.pid, request.resource_id);
		reply.senderPid = getpid();
		reply.requestType = requestType;
		reply.resource_id = request.resource_id;
		reply.lock_type = request.lock_type;
		reply.returnValue = error;
		send_reply(reply, sender);
		noResponse = 1;
		if (!error)
		{
			lock_waiter waiter = getFirstWaiter(request.resource_id);
			//jeżeli poprawny pid to zablokować i wysłać odpowiedź
			if (waiter.resource_lock.owner.pid)
			{
				message waiterReply;
				lock lockToMake;
				
				lockToMake = waiter.resource_lock;
				error = addLock(lockToMake);
				
				//wyłączyć timer jeśli istnieje
				timer_descriptor *desc = peekTimerDescriptor(waiter.resource_lock.owner.pid);
				if (desc != 0x0)
				{
					if (desc->timerid != 0)
					{
						timer_delete(desc->timerid);
					}
				}

				free(desc);

				//odpowiedź do waitera
				waiterReply.senderPid = getpid();
				waiterReply.requestType = DLM_REQ_LOCK;
				waiterReply.resource_id = waiter.resource_lock.resource_id;
				waiterReply.lock_type = lockToMake.lock_type;
				waiterReply.timeout = waiter.timeout;
				waiterReply.returnValue = error;
				send_reply(waiterReply, waiter.resource_lock.owner);
			}	
		}
		break;
	default:
		error = DLM_ERR_UKNOWN;
		printf("[E]DLM: Unknown request type.\n");
		break;
	}
	
	if (!noResponse) {
		reply.senderPid = getpid();
		reply.requestType = requestType;
		reply.resource_id = request.resource_id;
		reply.lock_type = request.lock_type;
		reply.returnValue = error;
		send_reply(reply, sender);
	}
	return error;
}

void on_timeout(sigval_t value)
{
	printf("Timeout\n");
	timer_descriptor *desc = (timer_descriptor*)value.sival_ptr;
	timer_delete(desc->timerid);
	removeWaiter(desc->waiter.resource_lock.owner.pid);
	removeTimerDescriptor(desc->waiter.resource_lock.owner.pid);

	//wysłać odpowiedź
	send_reply(desc->waiterReply, desc->waiter.resource_lock.owner);
	free(desc);
}
